package com.xiao.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.xiao.train.business.domain.*;
import com.xiao.train.business.dto.ConfirmOrderMQDto;
import com.xiao.train.business.enums.ConfirmOrderStatusEnum;
import com.xiao.train.business.enums.RedisKeyPreEnum;
import com.xiao.train.business.enums.SeatColEnum;
import com.xiao.train.business.enums.SeatTypeEnum;
import com.xiao.train.business.mapper.ConfirmOrderMapper;
import com.xiao.train.business.req.ConfirmOrderDoReq;
import com.xiao.train.business.req.ConfirmOrderQueryReq;
import com.xiao.train.business.req.ConfirmOrderTicketReq;
import com.xiao.train.business.resp.ConfirmOrderQueryResp;
import com.xiao.train.common.exception.BusinessException;
import com.xiao.train.common.exception.BusinessExceptionEnum;
import com.xiao.train.common.resp.PageResp;
import com.xiao.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class ConfirmOrderService {

    @Resource
    ConfirmOrderMapper confirmOrderMapper;

    @Resource
    DailyTrainTicketService dailyTrainTicketService;

    @Resource
    DailyTrainCarriageService dailyTrainCarriageService;

    @Resource
    DailyTrainSeatService dailyTrainSeatService;

    @Resource
    AfterConfirmOrderService afterConfirmOrderService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    SkTokenService skTokenService;

    @Autowired
    BeforeConfirmOrderService beforeConfirmOrderService;

    public void save(ConfirmOrderDoReq req) {
        DateTime now = DateTime.now();
        ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
        if (ObjectUtil.isNull(confirmOrder.getId())) {
            confirmOrder.setId(SnowUtil.getId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrder.setMemberId(req.getMemberId());
            confirmOrder.setStatus("S");
            confirmOrderMapper.insert(confirmOrder);
        } else {
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.updateByPrimaryKey(confirmOrder);
        }
    }

    /**
     * 降级方法
     * @param req
     * @param e
     */
    public void doConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
        log.info("购票请求被限流：{}", req);
        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
    }

    @SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
    public void doConfirm(ConfirmOrderMQDto dto) { //todo 难点一:业务逻辑繁琐,边界值容易出错
        String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + dto.getDate() + "-" + dto.getTrainCode();
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (!lock.tryLock(0, TimeUnit.SECONDS)) {
                log.info("未抢到锁,有其他线程正在出票,不做任何处理");
                return;
            }
            log.info("抢到锁了,lockKey:{}", lockKey);

            Date date = dto.getDate();
            String trainCode = dto.getTrainCode();
            while (true) {
                ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
                confirmOrderExample.setOrderByClause("id asc");
                confirmOrderExample.createCriteria()
                        .andTrainCodeEqualTo(trainCode).andDateEqualTo(date)
                        .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
                PageHelper.startPage(1, 5);
                List<ConfirmOrder> confirmOrders = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);
                if (CollUtil.isEmpty(confirmOrders)) {
                    log.info("找不到原始订单,结束循环");
                    break;
                }
                log.info("本次处理{}条订单", confirmOrders.size());
                for (ConfirmOrder confirmOrder : confirmOrders) {
                    try {
                        sell(confirmOrder);
                    } catch (BusinessException e) {
                        if (e.getE().equals(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR)) {
                            log.info("本订单余票不足,继续售卖下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
                            updateStatus(confirmOrder);
                        } else {
                            throw e;
                        }
                    }

                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock != null && lock.isHeldByCurrentThread()) { //todo 释放锁判断是否是当前线程的锁防止报错
                lock.unlock();
            }
        }
    }

    private void updateStatus(ConfirmOrder confirmOrder) {
        confirmOrderMapper.updateByPrimaryKey(confirmOrder);
    }

    private void sell(ConfirmOrder confirmOrder) {
        log.info("将确认订单更新成处理中,避免重复处理,confirm_order.id:{}", confirmOrder.getId());
        confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());

        List<ConfirmOrderTicketReq> reqTickets = JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class);
        Date date = confirmOrder.getDate();
        String trainCode = confirmOrder.getTrainCode();
        String start = confirmOrder.getStart();
        String end = confirmOrder.getEnd();
        //查出余票记录,得到真实库存
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
        log.info("查出余票记录: {}", dailyTrainTicket);
        //预扣余票数量,并判断余票数量是否足够
        reduceTickets(reqTickets, dailyTrainTicket);
        //最终的选座结果,作去重
        List<DailyTrainSeat> finalSeatList = new ArrayList<>();
        ConfirmOrderTicketReq ticketReq0 = reqTickets.get(0);
        if (StrUtil.isBlank(ticketReq0.getSeat())) {
            log.info("本次购票未选座");
            for (ConfirmOrderTicketReq reqTicket : reqTickets) {
                getSeat(date, trainCode, reqTicket.getSeatTypeCode(), null, null,
                        dailyTrainTicket.getStartIndex(), dailyTrainTicket.getEndIndex(),
                        finalSeatList);
            }
        } else {
            log.info("本次购票有选座");
            List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(ticketReq0.getSeatTypeCode());
            log.info("本次选座座位类型包含的列: {}", colEnumList);
            List<String> referSeatList = new ArrayList<>();
            for (int i = 1; i <= 2; i++) {
                for (SeatColEnum seatColEnum : colEnumList) {
                    referSeatList.add(seatColEnum.getCode() + i);
                }
            }
            log.info("用于作参照的两排座位: {}", referSeatList);
            List<Integer> absoluteOffsetList = new ArrayList<>();
            List<Integer> offsetList = new ArrayList<>();
            for (ConfirmOrderTicketReq reqTicket : reqTickets) {
                int index = referSeatList.indexOf(reqTicket.getSeat());
                absoluteOffsetList.add(index);
            }
            log.info("计算得到所有座位的绝对偏移值: {}", absoluteOffsetList);
            for (Integer index : absoluteOffsetList) {
                int offset = index - absoluteOffsetList.get(0);
                offsetList.add(offset);
            }
            log.info("计算得到所有座位的相对偏移值: {}", offsetList);
            getSeat(date, trainCode, ticketReq0.getSeatTypeCode(), "" +
                            ticketReq0.getSeat().charAt(0), offsetList,
                    dailyTrainTicket.getStartIndex(), dailyTrainTicket.getEndIndex(),
                    finalSeatList);
        }
        log.info("最终选座: {}", finalSeatList);
        //选中座位后的事务处理
        try {
            afterConfirmOrderService.afterDoConfirm(dailyTrainTicket, finalSeatList, reqTickets, confirmOrder);
        } catch (Exception e) {
            log.error("保存购票信息失败", e);
            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_EXCEPTION);
        }
    }

    private void getSeat(Date date, String trainCode, String seatType, String column,
                         List<Integer> offsetList, Integer startIndex, Integer endIndex,
                         List<DailyTrainSeat> finalSeatList) {
        List<DailyTrainSeat> curSeatList = new ArrayList<>();
        //一个车厢一个车厢的获取座位数据
        List<DailyTrainCarriage> carriages = dailyTrainCarriageService.selectBySeatType(date, trainCode, seatType);
        log.info("共查出{}个符合条件的车厢", carriages.size());
        for (DailyTrainCarriage carriage : carriages) {
            log.info("开始从车厢{}选座", carriage.getIndex());
            List<DailyTrainSeat> seats = dailyTrainSeatService.selectByCarriage(date, trainCode, carriage.getIndex());
            log.info("车厢{}的座位数:{}", carriage.getIndex(), seats.size());
            for (int i = 0; i < seats.size(); i++) {
                DailyTrainSeat seat = seats.get(i);
                // 判断当前座位不能被选中过
                boolean alreadyChooseFlag = false;
                for (DailyTrainSeat finalSeat : finalSeatList) {
                    if (finalSeat.getId().equals(seat.getId())) {
                        alreadyChooseFlag = true;
                        break;
                    }
                }
                if (alreadyChooseFlag) {
                    log.info("座位{}被选中过,不能重复选中,继续判断下一个座位", seat.getCarriageSeatIndex());
                    continue;
                }
                Integer seatIndex = seat.getCarriageSeatIndex();
                String col = seat.getCol();
                //判断column,有值的话比对列号
                if (StrUtil.isBlank(column)) {
                    log.info("无选座");
                } else {
                    if (!column.equals(col)) {
                        log.info("座位{}列值不对,继续判断下一个座位,当前列值{},目标列值{}",
                                seatIndex, col, column);
                        continue;
                    }
                }

                boolean isChoose = calSell(seat, startIndex, endIndex);
                if (isChoose) {
                    log.info("选中座位");
                    curSeatList.add(seat);
                } else {
                    continue;
                }
                //根据offset选剩下的座位
                boolean isGetAllOffsetSeat = true;
                if (CollUtil.isNotEmpty(offsetList)) {
                    log.info("有偏移值: {},校验偏移的座位是否可选", offsetList);
                    for (int j = 1; j < offsetList.size(); j++) {
                        Integer offset = offsetList.get(j);
                        //座位在库的索引从1开始
                        int nextIndex = i + offset;
                        if (nextIndex >= seats.size()) {
                            log.info("座位{}不可选,超出车厢最大座位号", nextIndex);
                            isGetAllOffsetSeat = false;
                            break;
                        }
                        DailyTrainSeat nextDailyTrainSeat = seats.get(nextIndex);
                        boolean isChooseNext = calSell(nextDailyTrainSeat, startIndex, endIndex);
                        if (isChooseNext) {
                            log.info("选中座位{}", nextDailyTrainSeat.getCarriageSeatIndex());
                            curSeatList.add(nextDailyTrainSeat);
                        } else {
                            log.info("座位{}不可选", nextDailyTrainSeat.getCarriageSeatIndex());
                            isGetAllOffsetSeat = false;
                            break;
                        }
                    }
                }
                if (!isGetAllOffsetSeat) {
                    curSeatList.clear();
                    continue;
                }
                //保存选好的座位
                finalSeatList.addAll(curSeatList);
                curSeatList.clear();
                return;
            }
        }
    }

    /**
     * 计算座位在区间内是否可卖
     * @param seat
     */
    private boolean calSell(DailyTrainSeat seat, Integer startIndex, Integer endIndex) {
        String sell = seat.getSell();
        String sellPart = sell.substring(startIndex, endIndex);
        if (sellPart.contains("1")) {
            log.info("座位{}在本区间{}-{}已售过票,不可选中该座位", seat.getCarriageSeatIndex(), startIndex, endIndex);
            return false;
        } else {
            log.info("座位{}在本区间{}-{}未售过票,可选中该座位", seat.getCarriageSeatIndex(), startIndex, endIndex);
            String newSellPart = sellPart.replace('0', '1');
            String newSell = sell.substring(0, startIndex) + newSellPart + sell.substring(endIndex);
            log.info("座位{}被选中,原售票信息:{},车站区间:{}-{}为{},最终售票信息:{}",
                    seat.getCarriageSeatIndex(), sell, startIndex, endIndex, sellPart, newSell);
            seat.setSell(newSell);
            return true;
        }
    }

    private void reduceTickets(List<ConfirmOrderTicketReq> ticketReqs, DailyTrainTicket dailyTrainTicket) {
        for (ConfirmOrderTicketReq ticketReq : ticketReqs) {
            String seatTypeCode = ticketReq.getSeatTypeCode();
            SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            switch (seatTypeEnum) {
                case YDZ -> {
                    int countLeft = dailyTrainTicket.getYdz() - 1;
                    if (countLeft < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setYdz(countLeft);
                }
                case EDZ -> {
                    int countLeft = dailyTrainTicket.getEdz() - 1;
                    if (countLeft < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setEdz(countLeft);
                }
                case RW -> {
                    int countLeft = dailyTrainTicket.getRw() - 1;
                    if (countLeft < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setRw(countLeft);
                }
                case YW -> {
                    int countLeft = dailyTrainTicket.getYw() - 1;
                    if (countLeft < 0) {
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setYw(countLeft);
                }
            }
        }
    }

    public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
        confirmOrderExample.setOrderByClause("id desc");
        log.info("查询页码: {}", req.getPage());
        log.info("每页条数: {}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<ConfirmOrder> confirmOrders = confirmOrderMapper.selectByExample(confirmOrderExample);

        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrders);
        log.info("总行数: {}", pageInfo.getTotal());
        log.info("总页数: {}", pageInfo.getPages());

        PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
        List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrders, ConfirmOrderQueryResp.class);
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    public void delete(Long id) {
        confirmOrderMapper.deleteByPrimaryKey(id);
    }

    /**
     * 查询前面有几个人在排队
     * @param id
     */
    public Integer queryLineCount(Long id) {
        ConfirmOrder confirmOrder = confirmOrderMapper.selectByPrimaryKey(id);
        ConfirmOrderStatusEnum statusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrder.getStatus());
        int result = switch (statusEnum) {
            case PENDING -> 0; // 排队0
            case SUCCESS -> -1; // 成功
            case FAILURE -> -2; // 失败
            case EMPTY -> -3; // 无票
            case CANCEL -> -4; // 取消
            case INIT -> 999; // 需要查表得到实际排队数量
        };

        if (result == 999) {
            // 排在第几位，下面的写法：where a=1 and (b=1 or c=1) 等价于 where (a=1 and b=1) or (a=1 and c=1)
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
            confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.PENDING.getCode());
            return Math.toIntExact(confirmOrderMapper.countByExample(confirmOrderExample));
        } else {
            return result;
        }
    }

    /**
     * 取消排队，只有I状态才能取消排队，所以按状态更新
     * @param id
     */
    public Integer cancel(Long id) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
        criteria.andIdEqualTo(id).andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
        ConfirmOrder confirmOrder = new ConfirmOrder();
        confirmOrder.setStatus(ConfirmOrderStatusEnum.CANCEL.getCode());
        return confirmOrderMapper.updateByExampleSelective(confirmOrder, confirmOrderExample);
    }
}
